﻿using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using Storm.TextEditor.Editor.CodeCompletion;
using Storm.TextEditor.Languages;
using Storm.TextEditor.Parser.Objects;
using Storm.TextEditor.Parser.XML;
using Moonlite.ExtensionApi.ControlInterfaces;
using Moonlite.ExtensionApi.GlobalInterfaces;
using Moonlite.Languages.vJass;
using Moonlite.Languages.vJass.CodeObj;
using Moonlite.Platform.General;
using Scope = Moonlite.Languages.vJass.CodeObj.Scope;

namespace Moonlite.Services.CodeCompletion
{
    public class JassCompletionDataProvider : ICompletionDataProvider
    {
        public const int ImageIndexLibrary = 0;
        public const int ImageIndexMethod = 1;
        public const int ImageIndexScope = 2;
        public const int ImageIndexStruct = 3;
        public const int ImageIndexVariable = 4;
        private static string[] UselessCharacters = new string[] {
            "(",
            ")",
            ".",
            "[",
            "]",
            "{",
            "}",
            ",",
            "+",
            "-",
            "*",
            "/",
            "%",
            "="
        };

        public static bool AddNativeData { get; set; }
        public bool AllowShow { get; set; }
        public bool UseAllowShow { get { return true; } }
        public ImageList ImageList { get; set; }
        public static ImageList GlobalImageList { get; set; }
        public CompletionDataCollection CompletionData { get; set; }
        public BackgroundWorker BackgroundWorker { get; set; }
        public JParser Parser { get; set; }
        public JParser LastParser { get; set; }
        public static CompletionDataCollection NativeData { get; set; }
        public bool FirstUpdate { get; set; }
        public bool ShouldUpdate { get; set; }
        public bool RequiresUpdate
        {
            get
            {
                bool libraries = LastParser.Libraries.Count != Parser.Libraries.Count;
                bool scopes = LastParser.Scopes.Count != Parser.Scopes.Count;
                bool structs = LastParser.Structs.Count != Parser.Structs.Count;
                bool globals = LastParser.Globals.Count != Parser.Globals.Count;
                bool functions = LastParser.Functions.Count != Parser.Functions.Count;

                foreach (Function function in LastParser.Functions)
                {
                    Function equalFunction = JassObjectHelper.GetFunctionFromName(function.Name);
                    if (equalFunction != null)
                    {
                        if (function.Variables.Count == equalFunction.Variables.Count)
                        {
                            continue;
                        }

                        functions = true;
                        break;
                    }
                }

                foreach (Struct structX in LastParser.Structs)
                {
                    if (structX != null)
                    {
                        Struct equalStruct = JassObjectHelper.GetStructFromName(structX.Name);
                        if (equalStruct != null)
                        {
                            foreach (Method method in structX.Methods)
                            {
                                Method equalMethod = JassObjectHelper.GetMethodFromName(equalStruct, method.Name);
                                if (equalMethod != null)
                                {
                                    if (method.Variables.Count == equalMethod.Variables.Count)
                                    {
                                        continue;
                                    }

                                    structs = true;
                                    break;
                                }
                            }

                            if (structX.Members.Count != equalStruct.Members.Count)
                            {
                                structs = true;
                                break;
                            }

                            if (structX.Methods.Count == equalStruct.Methods.Count)
                            {
                                continue;
                            }

                            structs = true;
                            break;
                        }
                    }
                }

                return libraries == true || scopes == true || structs == true || globals == true || functions == true || ShouldUpdate == true;
            }
        }

        private void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
        {
            if (Global.Instance.IsCurrentTabPageTextEditor() == true)
            {
                ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
                if (textEditor.InnerTextEditor.Document.Text != "")
                {
                    Parser.Parse(textEditor.InnerTextEditor.Document.Text);
                }
            }
        }

        private void OnBackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (RequiresUpdate != true && FirstUpdate != true)
            {
                return;
            }

            BuildCompletionData(true);
            FirstUpdate = false;
            ShouldUpdate = false;
        }

        public void Update()
        {
            if (BackgroundWorker.IsBusy == false)
            {
                BackgroundWorker.RunWorkerAsync();
            }

            try
            {
                LastParser = new JParser(Parser);
            }
            catch (Exception exception)
            {
                Logger.Log += exception.Message + Environment.NewLine;
            }
        }

        private void BuildCompletionData(bool includeNatives)
        {
            CompletionData.Clear();
            foreach (Library parsedLibrary in Parser.Libraries)
            {
                if (parsedLibrary.Parent == null)
                {
                    ProcessLibrary(parsedLibrary, true);
                }
            }

            foreach (Scope parsedScope in Parser.Scopes)
            {
                if (parsedScope.Parent == null)
                {
                    ProcessScope(parsedScope);
                }
            }

            foreach (Struct parsedStruct in Parser.Structs)
            {
                if (parsedStruct.Parent == null)
                {
                    ProcessStruct(parsedStruct, true, true);
                }
            }

            foreach (Globals parsedGlobals in Parser.Globals)
            {
                if (parsedGlobals.Parent == null)
                {
                    ProcessGlobals(parsedGlobals);
                }
            }

            foreach (Function parsedFunction in Parser.Functions)
            {
                if (parsedFunction.Parent == null)
                {
                    ProcessFunction(parsedFunction, true);
                }
            }

            if (AddNativeData == true && includeNatives == true)
            {
                CompletionData.AddRange(NativeData);
            }

            if (JassObjectHelper.GetStructFromCaretPosition() != null)
            {
                CompletionData.Add(new DefaultCompletionData(ImageIndexStruct, "this", "Represents the current instance of this struct"));
            }

            if (Global.Instance.IsCurrentTabPageTextEditor() == true)
            {
                ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
                textEditor.InnerTextEditor.CodeCompletionWindow.UpdateCompletionDataList();
            }
        }

        private void ProcessLibrary(Library parsedLibrary, bool shouldCaretBeInLibrary)
        {
            string description = String.Format("library {0}", parsedLibrary.Name);
            DefaultCompletionData libraryData = new DefaultCompletionData(ImageIndexLibrary, parsedLibrary.Name, description);
            bool execute = shouldCaretBeInLibrary == true && JassObjectHelper.IsCaretInObject(parsedLibrary) == false ? false : true;
            if (execute == true)
            {
                string inherits = parsedLibrary.Inherits.Replace(" ", "");
                string[] libraries = inherits.Split(',');
                Array.ForEach(libraries, library =>
                {
                    Library inheritLibrary = JassInheritanceManager.GetLibraryFromLibraryName(library);
                    if (inheritLibrary != null)
                        ProcessLibrary(inheritLibrary, false);
                });

                foreach (Function libraryFunction in parsedLibrary.Functions)
                {
                    ProcessFunction(libraryFunction, true);
                }

                foreach (Globals libraryGlobals in parsedLibrary.Globals)
                {
                    ProcessGlobals(libraryGlobals);
                }

                foreach (Scope libraryScope in parsedLibrary.Scopes)
                {
                    ProcessScope(libraryScope);
                }

                foreach (Struct libraryStruct in parsedLibrary.Structs)
                {
                    ProcessStruct(libraryStruct, true, true);
                }
            }

            CompletionData.Add(libraryData);
        }

        private void ProcessScope(Scope parsedScope)
        {
            string description = String.Format("scope {0}", parsedScope.Name);
            DefaultCompletionData scopeData = new DefaultCompletionData(ImageIndexScope, parsedScope.Name, description);

            if (JassObjectHelper.IsCaretInObject(parsedScope) == true)
            {
                foreach (Function scopeFunction in parsedScope.Functions)
                {
                    ProcessFunction(scopeFunction, true);
                }

                foreach (Globals scopeGlobals in parsedScope.Globals)
                {
                    ProcessGlobals(scopeGlobals);
                }

                foreach (Scope scopeScope in parsedScope.Scopes)
                {
                    ProcessScope(scopeScope);
                }

                foreach (Struct scopeStruct in parsedScope.Structs)
                {
                    ProcessStruct(scopeStruct, true, true);
                }
            }

            CompletionData.Add(scopeData);
        }

        private void ProcessStruct(Struct parsedStruct, bool normalMode, bool shouldCaretBeInStruct)
        {
            string description = String.Format("struct {0}{1}", parsedStruct.Name, (string.IsNullOrEmpty(parsedStruct.Extends) == false &&
                parsedStruct.Extends.Length > 0 ? "(extends " + parsedStruct.Extends : ""));

            DefaultCompletionData structData = new DefaultCompletionData(ImageIndexStruct, parsedStruct.Name, description);
            bool execute = normalMode == true && shouldCaretBeInStruct == true && JassObjectHelper.IsCaretInObject(parsedStruct) == false ? false : true;
            if (execute == true)
            {
                string inherits = parsedStruct.Extends.Replace(" ", "");
                string[] structs = inherits.Split(',');
                Array.ForEach(structs, @struct =>
                {
                    Struct inheritStruct = JassInheritanceManager.GetStructFromStructName(@struct);
                    if (inheritStruct != null)
                        ProcessStruct(inheritStruct, normalMode, false);
                });

                foreach (Method structMethod in parsedStruct.Methods)
                {
                    ProcessMethod(structMethod, normalMode);
                }

                foreach (Member structMember in parsedStruct.Members)
                {
                    description = String.Format("{0} {1}{2}", structMember.Type, structMember.Name, (structMember.Value.Length > 0 ? " = " +
                        structMember.Value : ""));

                    DefaultCompletionData memberData = new DefaultCompletionData(ImageIndexVariable, structMember.Name, description);
                    CompletionData.Add(memberData);
                }
            }

            if (normalMode == true)
            {
                CompletionData.Add(structData);
            }
        }

        private void ProcessGlobals(Globals parsedGlobals)
        {
            foreach (GlobalVariable global in parsedGlobals.GlobalVariables)
            {
                string description = String.Format("{0} {1}{2}", global.Type, global.Name, (global.Value.Length > 0 ? " = " +
                    global.Value : ""));

                DefaultCompletionData data = new DefaultCompletionData(ImageIndexVariable, global.Name, description);
                CompletionData.Add(data);
            }
        }

        private void ProcessFunction(Function parsedFunction, bool normalMode)
        {
            string description = String.Format("function {0} takes {1} returns {2}", parsedFunction.Name, parsedFunction.Arguments.Trim(),
                parsedFunction.Returns);

            DefaultCompletionData data = new DefaultCompletionData(ImageIndexMethod, parsedFunction.Name, description);

            if (JassObjectHelper.IsCaretInObject(parsedFunction) == true)
            {
                foreach (FunctionVariable variable in parsedFunction.Variables)
                {
                    if (variable.Argument == true && normalMode == false)
                    {
                        continue;
                    }

                    description = String.Format("{0} {1}{2}{3}", variable.Type, variable.Name, (variable.Value.Length > 0 ? " = " +
                        variable.Value : ""), (variable.Argument == true ? " (parameter)" : ""));

                    DefaultCompletionData variableData = new DefaultCompletionData(ImageIndexVariable, variable.Name, description);
                    CompletionData.Add(variableData);
                }
            }

            CompletionData.Add(data);
        }

        private void ProcessMethod(Method parsedMethod, bool normalMode)
        {
            string description = String.Format("method {0} takes {1} returns {2}", parsedMethod.Name, parsedMethod.Arguments.Trim(),
                parsedMethod.Returns);

            DefaultCompletionData data = new DefaultCompletionData(ImageIndexMethod, parsedMethod.Name, description);

            if (JassObjectHelper.IsCaretInObject(parsedMethod) == true)
            {
                foreach (MethodVariable variable in parsedMethod.Variables)
                {
                    if (variable.Argument == true && normalMode == false)
                    {
                        continue;
                    }

                    description = String.Format("{0} {1}{2}{3}", variable.Type, variable.Name, (variable.Value.Length > 0 ? " = " +
                        variable.Value : ""), (variable.Argument == true ? " (parameter)" : ""));

                    DefaultCompletionData variableData = new DefaultCompletionData(ImageIndexVariable, variable.Name, description);
                    CompletionData.Add(variableData);
                }
            }

            CompletionData.Add(data);
        }

        private static string GetWordTextFromColumn(Row row, int column)
        {
            ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
            if (textEditor != null)
            {
                Language currentLanguage = LanguageLoader.GetLanguageFrom(textEditor.InnerTextEditor.CurrentLanguage);
                string[] splitRow = row.Text.Split(currentLanguage.Separators.ToCharArray());
                int currentIndex = 0;
                foreach (string word in splitRow)
                {
                    if (column >= currentIndex && column <= currentIndex + word.Length + 1)
                    {
                        return word;
                    }

                    currentIndex += word.Length + 1;
                }
            }

            return null;
        }

        private static int GetWordIndexFromColumn(Row row, int column)
        {
            ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
            if (textEditor != null)
            {
                Language currentLanguage = LanguageLoader.GetLanguageFrom(textEditor.InnerTextEditor.CurrentLanguage);
                int currentIndex = 0;
                foreach (string word in row.Text.Split(currentLanguage.Separators.ToCharArray()))
                {
                    if (column >= currentIndex && column <= currentIndex + word.Length)
                    {
                        return currentIndex;
                    }

                    currentIndex += word.Length + 1;
                }
            }

            return -1;
        }

        public void UpdateInput()
        {
            ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
            if (textEditor != null)
            {
                Row row = textEditor.InnerTextEditor.Caret.CurrentRow;
                string rowText = row.Text.Trim();
                int dotIndex = rowText.IndexOf(".");
                if (dotIndex > -1)
                {
                    int caretX = textEditor.InnerTextEditor.Caret.Position.X;
                    textEditor.InnerTextEditor.Caret.Position.X = row.GetLeadingWhitespace().Length + dotIndex;
                    if (textEditor.InnerTextEditor.Caret.CurrentWord != null)
                    {
                        string identifier = GetIdentifierName(textEditor);
                        int identifierIndex = row.Text.IndexOf(identifier);
                        int currentWord = GetWordIndexFromColumn(row, textEditor.InnerTextEditor.Selection.LogicalBounds.FirstColumn);
                        if (currentWord > -1 && identifierIndex > -1)
                        {
                            if (currentWord > identifierIndex)
                            {
                                ShouldUpdate = true;
                                textEditor.InnerTextEditor.Caret.Position.X = caretX;
                                return;
                            }
                        }

                        int identifierDotIndex = identifier.IndexOf(".");
                        if (identifierDotIndex > -1)
                        {
                            identifier = identifier.Substring(0, identifierDotIndex);
                        }

                        string type = "";
                        Obj currentObj = JassObjectHelper.GetObjFromCaretPosition();
                        if (currentObj is Method)
                        {
                            Method currentMethod = currentObj as Method;
                            foreach (MethodVariable innerVariable in currentMethod.Variables)
                            {
                                if (innerVariable.Name != identifier)
                                {
                                    continue;
                                }

                                type = innerVariable.Type;
                                break;
                            }

                            foreach (Member member in (currentMethod.Parent as Struct).Members)
                            {
                                if (member.Name != identifier)
                                {
                                    continue;
                                }

                                type = member.Type;
                                break;
                            }
                        }

                        if (type != "")
                        {
                            Struct currentStruct = JassObjectHelper.GetStructFromName(type);
                            if (currentStruct != null)
                            {
                                CompletionData.Clear();
                                ProcessStruct(currentStruct, false, true);
                                if (textEditor.InnerTextEditor.CodeCompletionWindow.Visible == false)
                                {
                                    textEditor.InnerTextEditor.CodeCompletionWindow.UpdateCompletionDataList();
                                }
                            }
                        }
                    }

                    textEditor.InnerTextEditor.Caret.Position.X = caretX;
                }
            }
        }

        public void OnBeforeShow(char pressedChar, out bool cancel)
        {
            cancel = false;
        }
        public void OnSelectedItemChanged(ICompletionData selectedItem)
        {
            if (Global.Instance.Project.Type == ProjectType.Jass)
            {
                IFunctionExplorer functionExplorer = ((IFunctionExplorer)((object[])(Global.Instance.GetService(typeof(IFunctionExplorer))))[0]);
                ICodeExplorer codeExplorer = ((ICodeExplorer)((object[])(Global.Instance.GetService(typeof(ICodeExplorer))))[0]);
                JassCodeObject jassCodeObject = null;
                foreach (object item in functionExplorer.Items)
                {
                    JassCodeObject codeObject = (JassCodeObject)item;
                    if (codeObject.Name == selectedItem.Text)
                    {
                        jassCodeObject = codeObject;
                        break;
                    }
                }

                if (jassCodeObject == null)
                {
                    return;
                }

                codeExplorer.SetCode(jassCodeObject.Code);
            }
        }

        public void OnCodeCompleted(char completionCharacter, out bool show)
        {
            show = false;
            if (completionCharacter == '.')
            {
                show = DotTyped();
            }
        }

        public static bool CheckAllowShowWhenStructTyped()
        {
            ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
            textEditor.InnerTextEditor.Document.ParseAll();
            if (textEditor != null)
            {
                string identifier = GetIdentifierName(textEditor);
                Obj currentObj = JassObjectHelper.GetObjFromCaretPosition();
                Struct currentStruct = identifier == "this" ? JassObjectHelper.GetStructFromCaretPosition() : currentObj is Function ?
                    JassObjectHelper.GetLocalStructFromNameInFunction(identifier, (Function)currentObj) : currentObj is Method ?
                    JassObjectHelper.GetLocalStructFromNameInMethod(identifier, (Method)currentObj) : null;
                return currentStruct != null;
            }

            return false;
        }

        public bool DotTyped()
        {
            bool result = false;
            ITextEditor textEditor = Global.Instance.GetCurrentTabPageTextEditor();
            textEditor.InnerTextEditor.Document.ParseAll();
            if (textEditor != null)
            {
                string identifier = GetIdentifierName(textEditor);
                Obj currentObj = JassObjectHelper.GetObjFromCaretPosition();
                Struct currentStruct = currentObj is Function ? JassObjectHelper.GetLocalStructFromNameInFunction(identifier, (Function)currentObj) :
                    currentObj is Method ? JassObjectHelper.GetLocalStructFromNameInMethod(identifier, (Method)currentObj) : null;
                if (identifier == "" || identifier == "this")
                {
                    currentStruct = JassObjectHelper.GetStructFromCaretPosition();
                }

                CompletionData.Clear();
                if (currentStruct != null)
                {
                    foreach (Member member in currentStruct.Members)
                    {
                        string description = String.Format("{0} {1}{2}", member.Type, member.Name, (member.Value.Length > 0 ? " = " +
                            member.Value : ""));

                        DefaultCompletionData memberData = new DefaultCompletionData(ImageIndexVariable, member.Name, description);
                        CompletionData.Add(memberData);
                    }

                    foreach (Method method in currentStruct.Methods)
                    {
                        string description = String.Format("method {0} takes {1} returns {2}", method.Name, method.Arguments.Trim(),
                            method.Returns);

                        DefaultCompletionData methodData = new DefaultCompletionData(ImageIndexMethod, method.Name, description);
                        CompletionData.Add(methodData);
                    }

                    AllowShow = true;
                    textEditor.InnerTextEditor.CodeCompletionWindow.SelectIndex(0);
                    result = true;
                }

                textEditor.InnerTextEditor.CodeCompletionWindow.UpdateCompletionDataList();
                textEditor.InnerTextEditor.CodeCompletionWindow.Refresh();
                ShouldUpdate = true;
            }

            return result;
        }

        private static string RemoveUselessCharacters(string input)
        {
            Array.ForEach(UselessCharacters, uselessCharacter =>
            {
                int charIndex = input.IndexOf(uselessCharacter);
                if (charIndex > -1)
                {
                    input = input.Substring(0, charIndex);
                }
            });

            return input;
        }

        public static string GetIdentifierName(ITextEditor textEditor)
        {
            textEditor.InnerTextEditor.Caret.CurrentRow.Parse();
            string currentWord = GetWordTextFromColumn(textEditor.InnerTextEditor.Caret.CurrentRow, textEditor.InnerTextEditor.Caret.Position.X);
            if (string.IsNullOrEmpty(currentWord) == false)
            {
                string typeName = currentWord;
                typeName = RemoveUselessCharacters(typeName);
                Obj currentObj = JassObjectHelper.GetObjFromCaretPosition();
                if (currentObj is Method)
                {
                    Method currentMethod = currentObj as Method;
                    foreach (MethodVariable innerVariable in currentMethod.Variables)
                    {
                        if (innerVariable.Name == typeName)
                        {
                            return innerVariable.Type;
                        }
                    }

                    foreach (Member member in (currentMethod.Parent as Struct).Members)
                    {
                        if (member.Name == typeName)
                        {
                            return member.Type;
                        }
                    }
                }

                return typeName;
            }

            return "";
        }

        public void OnWindowHidden() { }
        public CompletionDataCollection CreateCompletionData()
        {
            Parser = new JParser();
            LastParser = new JParser();
            JassObjectHelper.Parser = Parser;
            AllowShow = true;
            FirstUpdate = true;

            BackgroundWorker = new BackgroundWorker();
            BackgroundWorker.DoWork += OnBackgroundWorkerDoWork;
            BackgroundWorker.RunWorkerCompleted += OnBackgroundWorkerRunWorkerCompleted;
            ImageList = GlobalImageList;
            CompletionData = new CompletionDataCollection();
            return CompletionData;
        }

        public JassCompletionDataProvider()
        {
            NativeData = JassNativeObjectsReader.Read();
        }

        static JassCompletionDataProvider()
        {
            GlobalImageList = new ImageList();
            GlobalImageList.TransparentColor = Color.Magenta;

            GlobalImageList.Images.Add("Library", Image.FromStream(typeof(JassCompletionDataProvider).Assembly.GetManifestResourceStream("Moonlite.Services.CodeCompletion.Resources.Icons.CodeCompletion.CCObject_Class.bmp")));
            GlobalImageList.Images.Add("Method", Image.FromStream(typeof(JassCompletionDataProvider).Assembly.GetManifestResourceStream("Moonlite.Services.CodeCompletion.Resources.Icons.CodeCompletion.CCObject_Method.bmp")));
            GlobalImageList.Images.Add("Scope", Image.FromStream(typeof(JassCompletionDataProvider).Assembly.GetManifestResourceStream("Moonlite.Services.CodeCompletion.Resources.Icons.CodeCompletion.CCObject_Enrichment.bmp")));
            GlobalImageList.Images.Add("Struct", Image.FromStream(typeof(JassCompletionDataProvider).Assembly.GetManifestResourceStream("Moonlite.Services.CodeCompletion.Resources.Icons.CodeCompletion.CCObject_Struct.bmp")));
            GlobalImageList.Images.Add("Variable", Image.FromStream(typeof(JassCompletionDataProvider).Assembly.GetManifestResourceStream("Moonlite.Services.CodeCompletion.Resources.Icons.CodeCompletion.CCObject_Field.bmp")));

            GlobalImageList.Images.SetKeyName(ImageIndexLibrary, "Library");
            GlobalImageList.Images.SetKeyName(ImageIndexMethod, "Method");
            GlobalImageList.Images.SetKeyName(ImageIndexScope, "Scope");
            GlobalImageList.Images.SetKeyName(ImageIndexStruct, "Struct");
            GlobalImageList.Images.SetKeyName(ImageIndexVariable, "Variable");
        }
    }
}
